[Sim Theme B] Launch menu & simulator lifecycle#23
Conversation
d2f0f14 to
44a7113
Compare
…ncurrency, reload reconnect, autostart (FR-B1..B8)
🤖 Automated PR reviewLaunch menu & lifecycle — Findings
✅ Security hardening is exemplary: launch/stop uses a registry-derived allowlist distinct from the restart allowlist, the id is charset-constrained by regex and validated against the registry, and only start/stop-by-container-id are issued — no exec/create/shell. Idempotent 204/304 handling, clean polling teardown in Recommendation: Approve; consider the reconcile-on-restart behavior before merge. |
jmservera
left a comment
There was a problem hiding this comment.
🤖 Automated line-anchored review — see the summary comment for the full findings table.
…im-theme-b-launch-menu
…im-theme-b-launch-menu
…im-theme-b-launch-menu
…im-theme-b-launch-menu
…im-theme-b-launch-menu
…im-theme-b-launch-menu
There was a problem hiding this comment.
Pull request overview
Implements Theme B (“Launch menu & simulator lifecycle”) by adding simulator launch/stop operations to the control plane and wiring the frontend Simulators menu to start/stop simulators, open/focus simulator panels, and keep simulator state refreshed while the menu is open.
Changes:
- Frontend: add launch/stop actions, simulator-state polling while the menu is open, and Golden Layout rebuild support for
sim:<id>panels via component state. - Control service: add allowlisted simulator start/stop endpoints and a boot-time autostart reconcile.
- Config/docs: document Theme B plan and add env/compose knobs for installed simulators + autostart selection.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| services/frontend/src/lib/panels.js | Adds simulator panel rebuild-from-state factory for sim:<id> Golden Layout components. |
| services/frontend/src/lib/control.js | Adds client helpers for POST /control/simulators/{id}/launch and /stop. |
| services/frontend/src/App.svelte | Implements menu actions (launch/stop), panel open/focus, and polling while Simulators menu is open. |
| services/control/simulators.js | Adds UBEROS_SIMULATORS_AUTOSTART override parsing for per-simulator autostart behavior. |
| services/control/server.js | Adds allowlisted simulator start/stop routes + container resolution and boot-time autostart reconcile logic. |
| docs/plans/sim-theme-b-launch-menu.md | Adds a Theme B plan stub documenting FR-B1..B8 scope. |
| compose.yaml | Adds control-service env vars for installed simulators and autostart override. |
| .env.example | Adds example env vars for installed simulators and autostart override (currently with invalid dotenv syntax). |
Comments suppressed due to low confidence (4)
services/control/server.js:257
reconcileAutostart()ignores the Docker API status code for the stop request. If Docker returns a non-204/304 status, it will be treated as success and autostart intent may not actually be enforced.
await dockerRequest('POST', `/containers/${container.Id}/stop`);
services/frontend/src/App.svelte:493
- Same
simBusyrace applies tostopSim: overlapping async actions can causesimBusyto be cleared by an earlier call while a later call is still pending. Guard against concurrent actions (or track busy per simulator).
async function stopSim(id) {
simBusy = id;
services/frontend/src/App.svelte:844
- While an action is in flight, other simulator buttons remain enabled (because
disabledonly matches the busy id). Combined with thesimBusyrace, this makes it easy to trigger overlapping actions that the UI state can't represent. Disabling all simulator actions whilesimBusyis non-null avoids this class of issues.
<button class="svc-reset" disabled={simBusy === sim.id} on:click={() => stopSim(sim.id)}>
services/frontend/src/App.svelte:848
- Same as above for the Launch button: leaving other rows enabled while one request is pending allows overlapping actions even though state is tracked with a single
simBusyvalue.
<button class="svc-reset" disabled={simBusy === sim.id} on:click={() => launchSim(sim.id)}>
|
PR summary update for Sim Theme B (Launch menu and simulator lifecycle) Resolved review feedback and applied targeted fixes:
Validation:
All previously unresolved review threads on this PR have been resolved. |
…, update BRD and ADR documentation, improve simulator lifecycle management, and add acceptance tests for Theme B functionality.
… health checks - Updated Theme A and C documentation with implementation details and status summaries. - Added acceptance tests for Theme C turtlesim functionality and Theme A framework lifecycle. - Improved stack health check to ensure all services report healthy status after launching. - Updated Theme F transport tests to validate gzweb routing and retirement of Gazebo noVNC.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 21 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (3)
services/control/server.js:248
- shouldRunAutostartReconcile() writes the autostart stamp file before reconciliation has actually succeeded. If Docker is unreachable or simulator containers aren’t created yet, reconcileAutostart() returns false but the stamp has already been written, so subsequent retry attempts will be skipped and autostart intent may never be enforced for this stack instance.
async function shouldRunAutostartReconcile() {
const stamp = STACK_INSTANCE || PROJECT;
try {
const prior = (await readFile(AUTOSTART_STAMP_FILE, 'utf8')).trim();
if (prior === stamp) return false;
services/control/server.js:270
- reconcileAutostart() returns undefined when shouldRunAutostartReconcile() is false (already reconciled). scheduleAutostartReconcile() treats that as “not done” and will keep retrying pointlessly. Returning a boolean here avoids unnecessary retries and keeps the function’s return contract consistent.
async function reconcileAutostart() {
if (!(await shouldRunAutostartReconcile())) return;
let containers;
tests/acceptance/s13-theme-f-transport.spec.js:47
- The total timing assertion (elapsedMs < 30_000) can fail even when both individual waits succeed, because the test allows up to 30s for #stream-status and then up to another 30s for #state (sequential waits). If the DOM updates are staggered, elapsedMs can exceed 30s and make the test flaky.
const started = Date.now();
await page.goto('/gzweb/');
await expect(page.locator('#stream-status')).toHaveText(/connected/i, { timeout: 30_000 });
await expect(page.locator('#state')).toHaveText(/connected/i, { timeout: 30_000 });
const elapsedMs = Date.now() - started;
// Informative ceiling to keep deterministic CI signal while documenting
// FR-F5 behavior in variable host environments.
expect(elapsedMs).toBeLessThan(30_000);
…or Theme B, C, and F
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
| const retries = Math.max(1, Number(process.env.UBEROS_AUTOSTART_RETRIES || 5)); | ||
| const retryMs = Math.max(250, Number(process.env.UBEROS_AUTOSTART_RETRY_MS || 1500)); | ||
| let attempts = 0; |
| setTimeout(() => { | ||
| scheduleAutostartReconcile(); | ||
| }, Number(process.env.UBEROS_AUTOSTART_DELAY_MS || 3000)); |
| function startSimPolling() { | ||
| refreshSimulators(); | ||
| stopSimPolling(); | ||
| simPollTimer = setInterval(refreshSimulators, 2500); | ||
| } |
| * FR-B6 evidence: reload reconnect and running-state persistence validated by `tests/acceptance/s10-theme-b-lifecycle.spec.js`. | ||
| * FR-B8 evidence: autostart override matrix validated by `tests/acceptance/s10-theme-b-lifecycle.spec.js` plus default env wiring in `.env.example` and `compose.yaml`. |
| // S10 - Theme B lifecycle acceptance coverage for FR-B6 and FR-B8. | ||
| // Verifies reload reconnect and autostart override semantics via control-plane | ||
| // simulator state without requiring direct Docker API assertions in test code. |
| const started = Date.now(); | ||
| await page.goto('/gzweb/'); | ||
| await expect(page.locator('#stream-status')).toHaveText(/connected/i, { timeout: 30_000 }); | ||
| await expect(page.locator('#state')).toHaveText(/connected/i, { timeout: 30_000 }); | ||
|
|
||
| const elapsedMs = Date.now() - started; |
| const retiredNoVnc = await request.get('/sim/gazebo/novnc/', { maxRedirects: 0 }); | ||
| expect([200, 404, 502]).toContain(retiredNoVnc.status()); | ||
| if (retiredNoVnc.status() === 200) { | ||
| const fallbackHtml = await retiredNoVnc.text(); | ||
| // Some proxy/frontend variants route unknown paths to the SPA shell. | ||
| // Accept that only when the body is clearly not a noVNC endpoint. | ||
| expect(fallbackHtml).not.toContain('noVNC'); | ||
| expect(fallbackHtml).toContain('<!doctype html'); | ||
| } |
Implements Theme B of the Simulation & Visualization PRD (FR-B1..B8). Lane 1 (foundation) — depends on Theme A (registry + GET /simulators). Adds launch/stop endpoints, concurrent + reload-persistent lifecycle, allowlisted start/stop, configurable autostart. Plan: docs/plans/sim-theme-b-launch-menu.md. Draft.